package com.zmn.mcc.dubbo.impl.cas;

import com.alibaba.fastjson.JSONObject;
import com.zmn.common.constant.StatusConsts;
import com.zmn.common.dto2.ResponseDTO;
import com.zmn.manager.common.database.annotation.ReadOnlyConnection;
import com.zmn.mcc.business.interfaces.staff.StaffBService;
import com.zmn.mcc.cache.StaffCache;
import com.zmn.mcc.cas.client.interfaces.CasRemoteService;
import com.zmn.mcc.cas.client.interfaces.DubboConstants;
import com.zmn.mcc.cas.core.CasConstants;
import com.zmn.mcc.cas.core.utils.SessionUtil;
import com.zmn.mcc.cas.model.*;
import com.zmn.mcc.cas.web.LoginStaffBuilder;
import com.zmn.mcc.cas.web.dao.RedisSessionDao;
import com.zmn.mcc.common.dto.permit.PermitLitDO;
import com.zmn.mcc.cache.SysCache;
import com.zmn.mcc.model.entity.sys.McSys;
import com.zmn.mcc.services.interfaces.permit.PermitService;
import com.zmn.mcc.services.interfaces.role.RoleService;
import com.zmn.mcc.services.interfaces.sys.SysService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.dubbo.config.annotation.Service;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.*;

/**
 * @author quanjic
 * @version v1.0
 * @since 2018/10/25 15:31
 **/
@Service(timeout = 5000, retries = 1, version = DubboConstants.VERSION)
public class CasRemoteServiceImpl implements CasRemoteService {

    private Logger logger = LoggerFactory.getLogger(CasRemoteServiceImpl.class);

    @Autowired
    private RedisSessionDao sessionDAO;

    @Autowired
    private SysService sysService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private PermitService permitService;

    @Autowired
    private SysCache sysCache;

    @Resource
    private StaffCache staffCache;


    @Resource
    private StaffBService staffBService;

    @Autowired
    private RedisTemplate redisTemplate;


    @Override
    @ReadOnlyConnection
    public ResponseDTO<String> serviceValidate(String ticket) {

        try {
            logger.debug("serviceValidate ticket:{}", ticket);
            Session session = sessionDAO.readSession(ticket);
            Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (obj == null) {
                logger.debug("session {} not exists", ticket);
                return ResponseDTO.fail("session " + ticket + " not exists");
            }

            SessionStatusDO sessionStatus = (SessionStatusDO) session.getAttribute(CasConstants.MC_SESSION_STATUS);
            if (sessionStatus != null && !sessionStatus.isOnline()) {
                logger.debug("#session# {} offline", ticket);
                return ResponseDTO.fail("session " + ticket + " not exists");
            }

            UserTokenDO userTokenDO = (UserTokenDO) ((SimplePrincipalCollection) obj).getPrimaryPrincipal();

            JSONObject root = new JSONObject();

            JSONObject serviceResponse = new JSONObject();

            JSONObject authenticationSuccess = new JSONObject();
            authenticationSuccess.put("user", userTokenDO.getUserKey());

            JSONObject attributes = new JSONObject();

            attributes.put("userToken", userTokenDO);
            attributes.put("ticket", ticket);

            authenticationSuccess.put("attributes", attributes);
            serviceResponse.put("authenticationSuccess", authenticationSuccess);
            root.put("serviceResponse", serviceResponse);
            return ResponseDTO.success(root.toJSONString());
        } catch (UnknownSessionException e) {
            logger.debug(e.getMessage(), e);
            return ResponseDTO.fail(e.getMessage());
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return ResponseDTO.fail(e.getMessage());
        }
    }

    @Override
    public ResponseDTO<String> readSession(Serializable sessionId) {
        try {
            Session session = sessionDAO.readSession(sessionId);
            SessionStatusDO sessionStatus = (SessionStatusDO) session.getAttribute(CasConstants.MC_SESSION_STATUS);
            if (sessionStatus != null && !sessionStatus.isOnline()) {
                logger.debug("#session# {} offline", sessionId);
                sessionDAO.delete(session);
                ResponseDTO<String> response = ResponseDTO.fail(CasConstants.FAIL_ANOTHER_LOGIN, "您的账号在其他设备或浏览器登录，您被迫退出，如不是您本人操作，请登录后及时修改密码(刷新页面即可重新登录)！");
                response.setData(SessionUtil.serialize(session));
                return response;
            }

            return ResponseDTO.success(SessionUtil.serialize(session));
        } catch (UnknownSessionException e) {
            logger.debug(e.getMessage(), e);
            return ResponseDTO.fail(StatusConsts.FAIL_NOTLOGIN, e.getMessage());
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return ResponseDTO.fail(e.getMessage());
        }
    }

    @Override
    @ReadOnlyConnection
    public ResponseDTO<String> getAuthorizationInfo(String ticket, Integer sysId) {
        try {
            logger.debug("getAuthorizationInfo ticket:{}, sysId:{}", ticket, sysId);
            Session session = sessionDAO.readSession(ticket);
            Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (obj == null) {
                logger.debug("session {} not exists", ticket);
                return ResponseDTO.fail("session " + ticket + " not exists");
            }
            UserTokenDO userTokenDO = (UserTokenDO) ((SimplePrincipalCollection) obj).getPrimaryPrincipal();
            LoginStaff staff = LoginStaffBuilder.build(userTokenDO);

            List<Integer> roles = doQueryRoles(staff);
            List<String> roles2 = null;
            if(CollectionUtils.isNotEmpty(roles)){
                roles2 = new ArrayList<>(roles.size());
                for(Integer role: roles){
                    roles2.add(String.valueOf(role));
                }
            }
            Collection<String> permitUrlSet = doQueryPermitUrl(sysId, roles);

            JSONObject jsonObject = new JSONObject(3);
            jsonObject.put("roles", roles2);
            jsonObject.put("permits", permitUrlSet);
            return ResponseDTO.success(jsonObject.toJSONString());
        } catch (UnknownSessionException e) {
            logger.debug(e.getMessage(), e);
            return ResponseDTO.fail(e.getMessage());
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return ResponseDTO.fail(e.getMessage());
        }
    }

    /**
     * 通过 ticket 与系统ID查询角色与权限以及用户信息
     *
     * @param ticket
     * @param sysId  系统ID
     * @return
     */
    @Override
    @ReadOnlyConnection
    public ResponseDTO<String> getAuthorizationInfoAndUserInfo(String ticket, Integer sysId) {
        try {
            logger.debug("getAuthorizationInfoAndUserInfo ticket:{}, sysId:{}", ticket, sysId);
            Session session = sessionDAO.readSession(ticket);
            Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (obj == null) {
                logger.debug("session {} not exists", ticket);
                return ResponseDTO.fail("session " + ticket + " not exists");
            }
            UserTokenDO userTokenDO = (UserTokenDO) ((SimplePrincipalCollection) obj).getPrimaryPrincipal();
            LoginStaff staff = LoginStaffBuilder.build(userTokenDO);
            List<Integer> roles = doQueryRoles(staff);
            Collection<String> permitUrlSet = doQueryPermitUrl(sysId, roles);

            JSONObject jsonObject = new JSONObject();
            jsonObject.put("roles", roles);
            jsonObject.put("permits", permitUrlSet);
            jsonObject.put("userInfo", LoginStaffBuilder.build(userTokenDO, session));
            return ResponseDTO.success(jsonObject.toJSONString(), "OK");
        } catch (UnknownSessionException e) {
            logger.debug(e.getMessage(), e);
            return ResponseDTO.fail(e.getMessage());
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return ResponseDTO.fail(e.getMessage());
        }
    }


    @Override
    public ResponseDTO<CasSysPermits> getCasSysPermits(String ticket) {
        LoginStaff staff = readLoginStaff(ticket);
        List<CasSysPermit> result = new ArrayList<>();
        List<McSys> list = sysCache.listApp();
        for (McSys sys : list) {
            Set<String> permits = this.doQueryPermitUrl(sys.getSysId(), staff.getRoleIds());
            result.add(new CasSysPermit(sys.getSysId(), permits));
        }
        return ResponseDTO.success(new CasSysPermits(result));
    }

    @ReadOnlyConnection
    @Override
    public ResponseDTO<List<CasSysDRO>> listApp() {
        try {
            List<McSys> list = sysCache.listApp();
            List<CasSysDRO> result = new ArrayList<>(list.size());
            for (McSys sys : list) {
                result.add(new CasSysDRO(sys.getSysId(), sys.getSysName(), sys.getUrl()));
            }
            return ResponseDTO.success(result);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return ResponseDTO.fail(e.getMessage());
        }
    }

    @ReadOnlyConnection
    @Override
    public ResponseDTO<List<CasSysDRO>> listApp(Boolean skip) {
        return listApp();
    }

    @Override
    public ResponseDTO<String> createSession(String session) {
        try {
            Serializable id = sessionDAO.create(SessionUtil.deserialize(session));
            return ResponseDTO.success(id.toString());
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return ResponseDTO.fail(e.getMessage());
        }
    }

    @Override
    public ResponseDTO updateSession(String session) {
        try {
            sessionDAO.update(SessionUtil.deserialize(session));
            return ResponseDTO.success();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return ResponseDTO.fail(e.getMessage());
        }
    }

    @Override
    public ResponseDTO deleteSession(String session) {
        try {
            sessionDAO.delete(SessionUtil.deserialize(session));
            return ResponseDTO.success();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return ResponseDTO.fail(e.getMessage());
        }
    }

    @Override
    public ResponseDTO setCachedAuthenticatedIpList(String redisKey, List<String> ipList) {
        try {
            redisTemplate.opsForValue().set(redisKey, ipList);
            return ResponseDTO.success(null);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return ResponseDTO.fail();
        }
    }

    @Override
    public ResponseDTO<Object> getCachedAuthenticatedIpList(String redisKey) {
        try {
            Object object = redisTemplate.opsForValue().get(redisKey);
            return ResponseDTO.success(object);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return ResponseDTO.fail();
        }
    }

    @Override
    public ResponseDTO<Boolean> isOpenDate(Integer staffId) {
        Boolean isOpenDate = staffCache.getOpenDateExists(staffId);
        return ResponseDTO.success(isOpenDate);
    }

    private List<Integer> doQueryRoles(LoginStaff staff) {
        return staff.getRoleIds();
    }

    private Set<String> doQueryPermitUrl(Integer sysId, List<Integer> roles) {
        Set<String> permitUrlSet = new HashSet<>();
        if (CollectionUtils.isNotEmpty(roles)) {
            List<PermitLitDO> list = permitService.listPermitLitBySysIdAndRoleIds(roles, sysId);
            for (PermitLitDO permit : list) {
                if (StringUtils.isNotEmpty(permit.getUrl())) {
                    permitUrlSet.add(permit.getUrl());
                }
            }
        }
        return permitUrlSet;
    }

    private LoginStaff readLoginStaff(String ticket){
        // 排查偶发性问题
        logger.info("=====readLoginStaff ticket={}",ticket);
        Session session = sessionDAO.readSession(ticket);
        Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
        if (obj == null) {
            return null;
        }
        return LoginStaffBuilder.build((UserTokenDO) ((SimplePrincipalCollection) obj).getPrimaryPrincipal());
    }

}
